/* Changes:
 *  - 64-bit fixes
 *  - BigEndian fixes
 *  - Enable sanity checking
 *  - Various bugfixes
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>
#include <ctype.h>

#define DEBUG 0
#define CHECK 1

typedef uint16_t le16_t;	/* Little-endian 16-bit */
typedef uint32_t le32_t;	/* Little-endian 32-bit */

//----------------Wave Stuff---------------------/
typedef struct wavHdr {
	uint8_t riff[4];
	le32_t bytestoend;
	uint8_t wavetxt[4];
	uint8_t fmttxt[4];
	le32_t formatsize;	// 16 byte format specifier
	le16_t format;		// Windows PCM
	le16_t channels;	// 2 channels 
	le32_t samplerate;	// 44,100 Samples/sec
	le32_t avgbyterate;	// 176,400 Bytes/sec
	le16_t samplebytes;	// 4 bytes/sample
	le16_t channelbits;	// 16 bits/channel
	uint8_t datatxt[4];
	le32_t blocksize;
} tWavHead;

#define HEADBYTES		36
#define WINDOWS_PCM		0x0001
//-------------------------------------------------/

/*  G L O B A L   D E F I N E S  */
typedef uint8_t			byte;
#define SIZERAW			2352
#define SIZEISO_MODE1		2048
#define SIZEISO_MODE2_RAW	2352
#define SIZEISO_MODE2_FORM1	2048
#define SIZEISO_MODE2_FORM2	2336
#define AUDIO			0
#define MODE1			1
#define MODE2			2
#define MODE1_2352		10
#define MODE2_2352		20
#define MODE1_2048		30
#define MODE2_2336		40

#define PROG_INTERVAL		1024
#define UNKNOWN			-1
#define OFFSET			150
// got this from easycd pro by looking at a blank disk so it may be off...
#define CD74_MAX_SECTORS	334873	// 653.75 Mb

static inline le16_t cpu_to_le16(uint16_t value)
{
	uint8_t *bytes = (uint8_t *)&value;
	le16_t ret;

	ret = bytes[0];
	ret |= (uint16_t)(bytes[1]) << 8;

	return ret;
}

static inline le32_t cpu_to_le32(uint32_t value)
{
	uint8_t *bytes = (uint8_t *)&value;
	le32_t ret;

	ret = bytes[0];
	ret |= (uint32_t)(bytes[1]) << 8;
	ret |= (uint32_t)(bytes[2]) << 16;
	ret |= (uint32_t)(bytes[3]) << 24;

	return ret;
}

unsigned long int Index(char m, char s, char f)
{
	unsigned long int temp;

	temp = (((m >> 4) * 10) + (m & 0xf)) * 60;
	temp = (temp + (((s >> 4) * 10) + (s & 0xf))) * 75;
	temp = temp + (((f >> 4) * 10) + (f & 0xf));

	return temp;
}

void unIndex(unsigned long int index, char *ptr)
{
	char m, s, f;

	f = (char)(index % 75);
	s = (char)((index / 75) % 60);
	m = (char)(index / (75 * 60));
	sprintf(ptr, "%d%d:%d%d:%d%d", m / 10, m % 10, s / 10, s % 10, f / 10, f % 10);

}

// global variables
static FILE *fdBinFile;
static FILE *fdCueFile;
static FILE *fdOutFile;
static char sBinFilename[PATH_MAX];
static char sOutFilename[PATH_MAX];

static unsigned long int writepos = 0;	// for inplace conversions...

#define OUTBUF_SIZE 4*1024*1024
#define INBUF_SIZE 4*1024*1024
static unsigned char OUTBUF[OUTBUF_SIZE];
static unsigned int OUTBUF_IDX = 0;
static unsigned char INBUF[INBUF_SIZE];
static unsigned int INBUF_RIDX = 0;
static unsigned int INBUF_WIDX = 0;

static int mode2to1 = 0;

typedef struct track {
	unsigned short mode;
	unsigned long idx0;
	unsigned long idx1;
	char num[3];
	char name[80];
	unsigned long offset0;
	unsigned long offset1;
	unsigned long size;	/* track size in bytes */
} tTrack;

static int buffered_fread(unsigned char *array, unsigned int size)
{
	unsigned int i;

	if (INBUF_WIDX == 0) {
		INBUF_WIDX += fread(INBUF, 1, (INBUF_SIZE / size) * size, fdBinFile);
	}
	if (INBUF_WIDX == 0)
		return 0;	// read failed.

	for (i = 0; i < size; i++) {

		array[i] = INBUF[INBUF_RIDX++];
		if ((INBUF_RIDX == INBUF_WIDX) && (i < (size - 1))) {
			printf("   Warning: Premature EOF\n");
			while (i++ < size) {
				array[i] = 0;
			}	/* zero fill the rest */
			break;
		}
	}

	if (INBUF_RIDX == INBUF_WIDX) {
		INBUF_RIDX = 0;
		INBUF_WIDX = 0;
	}

	return 1;		// read passed

}

static void buffered_fwrite(unsigned char *array, unsigned int size)
{
	unsigned int idx;
	unsigned long int readpos = 0;

	if (OUTBUF_IDX + size >= OUTBUF_SIZE) {

		if (fdOutFile == fdBinFile) {
			readpos = ftell(fdOutFile);
			if (0 != fseek(fdOutFile, writepos, SEEK_SET)) {
				perror("\nbin2iso(fseek)");
				exit(1);
			}
		}
		//      printf("\nWriting            \n");
		if (1 != fwrite(OUTBUF, OUTBUF_IDX, 1, fdOutFile)) {
			perror("\nbin2iso(fwrite)");
			fclose(fdOutFile);
			// remove(sOutFilename);
			exit(1);
		}
		if (1 != fwrite(array, size, 1, fdOutFile)) {
			perror("\nbin2iso(fwrite)");
			fclose(fdOutFile);
			// remove(sOutFilename);
			exit(1);
		}
//      printf("\nWrote %d bytes            \n", OUTBUF_IDX+size);
		OUTBUF_IDX = 0;

		if (fdOutFile == fdBinFile) {
			writepos = ftell(fdOutFile);
			if (0 != fseek(fdOutFile, readpos, SEEK_SET)) {
				perror("\nbin2iso(fseek)");
				exit(1);
			}
		}

	} else {
		for (idx = 0; idx < size; idx++) {
			OUTBUF[OUTBUF_IDX + idx] = array[idx];
		}
		OUTBUF_IDX += size;
	}

}

static void flush_buffers(void)
{
	unsigned long int readpos = 0;

	if (fdOutFile == fdBinFile) {
		readpos = ftell(fdOutFile);
		if (0 != fseek(fdOutFile, writepos, SEEK_SET)) {
			perror("\nbin2iso(fseek)");
			exit(1);
		}
	}

	if (1 != fwrite(OUTBUF, OUTBUF_IDX, 1, fdOutFile)) {
		perror("\nbin2iso(fwrite)");
		fclose(fdOutFile);
		// remove(sOutFilename);
		exit(1);
	}
//	printf("\nWrote %d bytes          \n", OUTBUF_IDX);
	OUTBUF_IDX = 0;
	INBUF_RIDX = 0;
	INBUF_WIDX = 0;

	if (fdOutFile == fdBinFile) {
		writepos = ftell(fdOutFile);
		if (0 != fseek(fdOutFile, readpos, SEEK_SET)) {
			perror("\nbin2iso(fseek)");
			exit(1);
		}
	}

}

// presumes Line is preloaded with the "current" line of the file
static int getTrackinfo(char *Line, tTrack * track)
{
//	char tnum[3];
	char inum[3];
	char min;
	char sec;
	char block;

	track->idx0 = -1;
	track->idx1 = -1;

	// Get the 'mode'
	if (strncmp(&Line[2], "TRACK ", 6) == 0) {
		strncpy(track->num, &Line[8], 2);
		track->num[2] = '\0';

		track->mode = UNKNOWN;
		if (strncmp(&Line[11], "AUDIO", 5) == 0)
			track->mode = AUDIO;
		if (strncmp(&Line[11], "MODE1/2352", 10) == 0)
			track->mode = MODE1_2352;
		if (strncmp(&Line[11], "MODE1/2048", 10) == 0)
			track->mode = MODE1_2048;
		if (strncmp(&Line[11], "MODE2/2352", 10) == 0)
			track->mode = MODE2_2352;
		if (strncmp(&Line[11], "MODE2/2336", 10) == 0)
			track->mode = MODE2_2336;
	} else
		return (1);

	// Set the name
	strcpy(track->name, sBinFilename);
	track->name[strlen(sBinFilename) - 4] = '\0';
	strcat(track->name, "-");
	strcat(track->name, track->num);

	if ((track->mode == MODE1_2352) ||
	    (track->mode == MODE1_2048) ||
	    (track->mode == MODE2_2352) || (track->mode == MODE2_2336)) {
		strcat(track->name, ".iso");
	} else if (track->mode == AUDIO) {
		strcat(track->name, ".wav");
	} else {
		printf("Track %s Unsupported mode\n", track->num);
		return (1);
	}

	// Get the track indexes
	while (1) {
		if (!fgets(Line, 256, fdCueFile)) {
			break;
		}

		if (strncmp(&Line[2], "TRACK ", 6) == 0) {
			break;	// next track starting
		}

		if (strncmp(&Line[4], "INDEX ", 6) == 0) {
			strncpy(inum, &Line[10], 2);
			inum[2] = '\0';
			min = ((Line[13] - '0') << 4) | (Line[14] - '0');
			sec = ((Line[16] - '0') << 4) | (Line[17] - '0');
			block = ((Line[19] - '0') << 4) | (Line[20] - '0');

			if (strcmp(inum, "00") == 0)
				track->idx0 = Index(min, sec, block);
			else if (strcmp(inum, "01") == 0)
				track->idx1 = Index(min, sec, block);
			else {
				printf("Unexpected Index number: %s\n", inum);
				exit(1);
			}

		} else if (strncmp(&Line[4], "PREGAP ", 7) == 0) {;	/* ignore, handled below */
		} else if (strncmp(&Line[4], "FLAGS ", 6) == 0) {;	/* ignore */
		} else {
			printf("Unexpected cuefile line: %s\n", Line);
		}
	}
	if (track->idx0 == -1)
		track->idx0 = track->idx1;
	if (track->idx1 == -1)
		track->idx1 = track->idx0;
	return (0);
}

static void dotrack(short mode, long preidx, long startidx, long endidx, unsigned long offset)
{
	unsigned char buf[SIZERAW + 100];
	unsigned long blockswritten = 0;
	unsigned int uiLastIndex;
#if CHECK
	unsigned int uiCurrentIndex;
#endif
	unsigned int write = 1;

	tWavHead wavhead = { "RIFF",
		cpu_to_le32(0),
		"WAVE",
		"fmt ",
		cpu_to_le32(16),		// 16 byte format specifier
		cpu_to_le16(WINDOWS_PCM),	// format
		cpu_to_le16(2),			// 2 Channels 
		cpu_to_le32(44100),		// 44,100 Samples/sec    
		cpu_to_le32(176400),		// 176,400 Bytes/sec
		cpu_to_le16(4),			// 4 bytes/sample
		cpu_to_le16(16),		// 16 bits/channel
		"data",
		cpu_to_le32(0),
	};

	uiLastIndex = startidx - 1;
	// Input -- process -- Output 
	if (startidx != 0)
		printf("\nNote: PreGap = %ld frames\n", startidx - preidx);
	else
		printf("\nNote: PreGap = %d frames\n", OFFSET);	// cd standard: starting offset
	// - of course this isn't true for bootable cd's...

	if (sOutFilename[0] != '\0') {
		printf("Creating %s (%06ld,%06ld) ", sOutFilename, startidx, endidx - 1);
	} else {
		printf("Converting (%06ld,%06ld) ", startidx, endidx - 1);
	}
	switch (mode) {
	case AUDIO:
		printf("Audio");
		break;
	case MODE1_2352:
		printf("Mode1/2048");
		break;
	case MODE2_2336:
		printf("Mode2/2352");
		break;
	case MODE2_2352:
		if (mode2to1 != 1)
			printf("Mode2/2352");
		else
			printf("Mode1/2048");
		break;
	case MODE1_2048:
		printf("Mode1/2048");
		break;
	default:
		printf("Huh? What's going on?");
		exit(1);
	}
	printf(" :       ");

	if (sOutFilename[0] != '\0') {
		if (NULL == (fdOutFile = fopen(sOutFilename, "wb"))) {
			perror("bin2iso(fopen)");
		}
//	printf("\nOpened File %s: %d\n", sOutFilename, fdOutFile);

	} else {
		fdOutFile = fdBinFile;
	}
	if (fdOutFile == NULL) {
		printf("    Unable to create %s\n", sOutFilename);
		exit(1);
	}

	if (0 != fseek(fdBinFile, offset, SEEK_SET)) {
		perror("\nbin2iso(fseek)");
		exit(1);
	}
#if (DEBUG == 0)
	if (mode == AUDIO) {
		if (1 != fwrite(&wavhead, sizeof(wavhead), 1, fdOutFile)) {	// write placeholder
			perror("\nbin2iso(fwrite)");
			fclose(fdOutFile);
			// remove(sOutFilename);
			exit(1);
		}
	}
#endif

	memset(&buf[0], '\0', sizeof(buf));
	if (mode == MODE2_2336) {
		unsigned int M = 0, S = 2, F = 0;
		while (buffered_fread(&buf[16], SIZEISO_MODE2_FORM2)) {
			//setup headed area (probably not necessary though...
			//buf[0] = 0;
			memset(&buf[1], 0xFF, sizeof(buf[0]) * 10);
			//buf[11] = 0;
			buf[12] = M;
			buf[13] = S;
			buf[14] = F;
			buf[15] = MODE2;

			if ((++F & 0xF) == 0xA)
				F += 6;

			if (F == 0x75) {
				S++;
				F = 0;
			}
			if ((S & 0xF) == 0xA)
				S += 6;

			if (S == 0x60) {
				M++;
				S = 0;
			}
			if ((M & 0xF) == 0xA)
				M += 6;
//			printf("\n%x:%x:%x", M, S, F);

			buffered_fwrite(buf, SIZERAW);
			uiLastIndex++;
			memset(&buf[0], '\0', sizeof(buf));
			if (startidx % PROG_INTERVAL == 0) {
				printf("\b\b\b\b\b\b%06ld", startidx);
			}
			if (++startidx == endidx) {
				printf("\b\b\b\b\b\bComplete\n");
				break;
			}
		}
	} else if (mode == MODE1_2048) {
		while (buffered_fread(buf, SIZEISO_MODE1)) {
			buffered_fwrite(buf, SIZEISO_MODE1);
			uiLastIndex++;
			if (startidx % PROG_INTERVAL == 0) {
				printf("\b\b\b\b\b\b%06ld", startidx);
			}
			if (++startidx == endidx) {
				printf("\b\b\b\b\b\bComplete\n");
				break;
			}
		}
	} else {
		while (buffered_fread(buf, SIZERAW)) {
			switch (mode) {
			case AUDIO:
#if (DEBUG == 0)
				buffered_fwrite(buf, SIZERAW);
#endif
				uiLastIndex++;
				blockswritten++;
				break;
			case MODE1_2352:
				// should put a crc check in here...
#if CHECK
				if (buf[15] != MODE1) {
					printf("\nWarning: Mode Error in bin file!\n");
					printf("   %02x:%02x:%02x : mode %02x\n", buf[12], buf[13],
					       buf[14], buf[15]);
					//exit(1);
				}

				uiCurrentIndex = Index(buf[12], buf[13], buf[14]) - OFFSET;

				if (uiCurrentIndex != uiLastIndex + 1) {
					printf("\nWarning: Frame Error in bin file!\n");
					printf("Last      %02d:%02d:%02d (%d)\n",
					       ((uiLastIndex + OFFSET) / 75) / 60,
					       ((uiLastIndex + OFFSET) / 75) % 60,
					       (uiLastIndex + OFFSET) % 75, uiLastIndex);
					printf("Current   %02x:%02x:%02x (%d)\n", buf[12], buf[13],
					       buf[14], uiCurrentIndex);
					printf("Expecting %02d:%02d:%02d (%d)\n",
					       ((uiLastIndex + OFFSET + 1) / 75) / 60,
					       ((uiLastIndex + OFFSET + 1) / 75) % 60,
					       (uiLastIndex + OFFSET + 1) % 75, uiLastIndex + 1);

				}
#endif
#if (DEBUG == 0)
				buffered_fwrite(&buf[16], SIZEISO_MODE1);
#endif
#if CHECK
				uiLastIndex = uiCurrentIndex;
#endif
				break;
			case MODE2_2352:
#if CHECK
				if ((buf[15] & 0xf) != MODE2) {
					printf("\nWarning: Mode Error in bin file!\n");
					printf("   %02x:%02x:%02x : mode %02x\n", buf[12], buf[13],
					       buf[14], buf[15]);
					//exit(1);
				}

				uiCurrentIndex = Index(buf[12], buf[13], buf[14]) - OFFSET;

				if (uiCurrentIndex != uiLastIndex + 1) {
					printf("\nWarning: Frame Error in bin file!\n");
					printf("Last      %02d:%02d:%02d (%d)\n",
					       ((uiLastIndex + OFFSET) / 75) / 60,
					       ((uiLastIndex + OFFSET) / 75) % 60,
					       (uiLastIndex + OFFSET) % 75, uiLastIndex);
					printf("Current   %02x:%02x:%02x (%d)\n", buf[12], buf[13],
					       buf[14], uiCurrentIndex);
					printf("Expecting %02d:%02d:%02d (%d)\n",
					       ((uiLastIndex + OFFSET + 1) / 75) / 60,
					       ((uiLastIndex + OFFSET + 1) / 75) % 60,
					       (uiLastIndex + OFFSET + 1) % 75, uiLastIndex + 1);
				}
#endif
#if (DEBUG == 0)
				if (mode2to1)
					buffered_fwrite(&buf[16 + 8], SIZEISO_MODE1);
				else if (write)
					buffered_fwrite(&buf[0], SIZEISO_MODE2_RAW);
#endif
#if CHECK
				uiLastIndex = uiCurrentIndex;
#endif
				break;
			default:
				printf("Unkown Mode\n");
				exit(1);
				break;
			}

			memset(&buf[0], '\0', sizeof(buf));
			if (startidx % PROG_INTERVAL == 0) {
				printf("\b\b\b\b\b\b%06ld", startidx);
			}
			if (++startidx == endidx) {
				printf("\b\b\b\b\b\bComplete\n");
				break;
			}
		}
	}
	flush_buffers();	// flushes write buffer
	// and clears read buffer.
	if (mode == AUDIO) {
		wavhead.blocksize = cpu_to_le32(blockswritten * SIZERAW);
		wavhead.bytestoend = cpu_to_le32((blockswritten * SIZERAW) + HEADBYTES);
		// rewind to the beginning
		if (0 != fseek(fdOutFile, 0, SEEK_SET)) {
			perror("\nbin2iso(fseek)");
			exit(1);
		}
#if (DEBUG == 0)
		fwrite(&wavhead, sizeof(wavhead), 1, fdOutFile);
#endif
	}
	fclose(fdOutFile);
}

static void doCueFile(void)
{
	int track = 1;
	unsigned long int binIndex = 0;
	unsigned long int trackIndex = 0;
	const int gapThreshold = 20;	// look for 0.266 sec gap
	const int valueThreshold = 800;	// look for samples < 700
	int count = 0;
	int i, blank;
	int gapon = 0;
	short value;

	char mode[12] = "AUDIO";
	char index0[9] = "00:00:00";
	char index1[9] = "00:00:00";
	unsigned char buf[SIZERAW + 100];

	printf("FILE %s BINARY\n", sBinFilename);
	fprintf(fdCueFile, "FILE %s BINARY\n", sBinFilename);
	memset(buf, '\0', sizeof(buf));
	while (fread(buf, 1, SIZERAW, fdBinFile)) {
		if (trackIndex == 0) {
			if ((buf[0] == 0x00) &&
			    (buf[1] == 0xFF) &&
			    (buf[2] == 0xFF) &&
			    (buf[3] == 0xFF) &&
			    (buf[4] == 0xFF) &&
			    (buf[5] == 0xFF) &&
			    (buf[6] == 0xFF) &&
			    (buf[7] == 0xFF) &&
			    (buf[8] == 0xFF) &&
			    (buf[9] == 0xFF) && (buf[10] == 0xFF) && (buf[11] == 0x00)
			    ) {
				sprintf(mode, "MODE%d/2352", buf[15]);
			} else {
				sprintf(mode, "AUDIO");
			}
		}
		if (binIndex == 0) {
			printf("  TRACK %02d %s\n", track, mode);
			fprintf(fdCueFile, "  TRACK %02d %s\n", track, mode);
			printf("    INDEX 01 %s\n", index0);
			fprintf(fdCueFile, "    INDEX 01 %s\n", index0);
		}
		blank = 1;
		for (i = 0; i < SIZERAW; i += 2) {
			value = buf[i + 1];
			value = ((value << 8) | buf[i]);
//			printf("%f %i\n",(1.0/75)*binIndex, value);
			if (abs(value) > valueThreshold) {
				blank = 0;
				break;
			}
		}
//		if(i == SIZERAW) printf("%f ~blank~\n", (1.0/75)*binIndex);
		if (blank == 1)
			count++;
		else if (gapon == 1) {
			gapon = 0;
			unIndex(binIndex - count, index0);
			count = 0;
			unIndex(binIndex, index1);
			printf("  TRACK %02d %s\n", track, mode);
			fprintf(fdCueFile, "  TRACK %02d %s\n", track, mode);
			printf("    INDEX 00 %s\n", index0);
			fprintf(fdCueFile, "    INDEX 00 %s\n", index0);
			printf("    INDEX 01 %s\n", index1);
			fprintf(fdCueFile, "    INDEX 01 %s\n", index1);
		}

		if ((count > gapThreshold) && (gapon == 0)) {
			gapon = 1;
			track++;
			trackIndex = -1;
		}

		memset(buf, '\0', sizeof(buf));
		binIndex++;
		trackIndex++;
	}
}

// return 0 to when no data found, 1 when there is.
static int checkGaps(FILE * fdBinFile, tTrack tracks[], int nTracks)
{
	int i, k;
	unsigned long int j;
	unsigned char buf[SIZERAW];
	int writegap = 0;
	short value;
	int count;

	if (nTracks == 2) {
		return 0;
	};			// don't need to bother with single track images

	printf("Checking gap data:\n");

	for (i = 0; i < nTracks; i++) {
		if ((tracks[i].offset0 != tracks[i].offset1) && (tracks[i - 1].mode == AUDIO)) {
			if (0 != fseek(fdBinFile, tracks[i].offset0, SEEK_SET)) {
				perror("\nbin2iso(fseek)");
				exit(1);
			}
			count = 0;
			for (j = tracks[i].idx0; j < tracks[i].idx1; j++) {
				if (0 == fread(buf, SIZERAW, 1, fdBinFile)) {
					perror("bin2iso(fread)");
					exit(1);
				}
				for (k = 0; k < SIZERAW; k += 2) {
					value = buf[k + 1];
					value = ((value << 8) | buf[k]);
					if (value != 0) {
						count++;

						// printf("%10d: %2x\n", count ,value );
					}
				}
			}
			if (count != 0) {
				printf
				    ("   Track%02d - %d values of Non-Zero gap data encountered\n",
				     i - 1, count);
				if ((count > SIZERAW / 2 / 2) && (writegap == 0)) {
					printf("   -->Threashold reached\n");
					writegap = 1;
				}
			}
		}
	}
	return writegap;
}

static void string_tolower(char *str, size_t len)
{
	while (len && *str) {
		*str = tolower(*str);
		str++;
		len--;
	}
}

static void string_toupper(char *str, size_t len)
{
	while (len && *str) {
		*str = toupper(*str);
		str++;
		len--;
	}
}

static FILE * fopen_filecase(const char *path, const char *file, const char *openmode)
{
	char filename[PATH_MAX] = { 0, };
	char final[PATH_MAX] = { 0, };
	FILE *fd;
	char *pos;

	/* Try original */
	strncpy(filename, file, sizeof(filename) - 1);
	snprintf(final, sizeof(final), "%s/%s", path, filename);
	fd = fopen(final, openmode);
	if (fd)
		goto ok;

	/* Try lower.lower */
	strncpy(filename, file, sizeof(filename) - 1);
	string_tolower(filename, (size_t)-1);
	snprintf(final, sizeof(final), "%s/%s", path, filename);
	fd = fopen(final, openmode);
	if (fd)
		goto ok;

	/* Try UPPER.UPPER */
	strncpy(filename, file, sizeof(filename) - 1);
	string_toupper(filename, (size_t)-1);
	snprintf(final, sizeof(final), "%s/%s", path, filename);
	fd = fopen(final, openmode);
	if (fd)
		goto ok;

	/* Try lower.UPPER */
	strncpy(filename, file, sizeof(filename) - 1);
	string_tolower(filename, (size_t)-1);
	pos = strrchr(filename, '.');
	if (pos)
		string_toupper(pos, (size_t)-1);
	snprintf(final, sizeof(final), "%s/%s", path, filename);
	fd = fopen(final, openmode);
	if (fd)
		goto ok;

	/* Try UPPER.lower */
	strncpy(filename, file, sizeof(filename) - 1);
	string_toupper(filename, (size_t)-1);
	pos = strrchr(filename, '.');
	if (pos)
		string_tolower(pos, (size_t)-1);
	snprintf(final, sizeof(final), "%s/%s", path, filename);
	fd = fopen(final, openmode);
	if (fd)
		goto ok;

	return NULL;

ok:
	printf("%s: Opening file %s\n", file, final);

	return fd;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ */

int main(int argc, char **argv)
{
//	int printon = 0;

	char sLine[256];
	int i, j, q;

//	int writegap = -1;   // auto detect pregap data action by default. 
	int writegap = 1;	// keep pregap data by default. 
	int no_overburn = 0;
	int createCue = 0;
	char sTrack[3] = "00";
	int doOneTrack = 0;
	int doInPlace = 0;

	tTrack trackA;
	tTrack trackB;

	tTrack tracks[100];
	int nTracks = 0;

	char sOutdir[PATH_MAX];
	const char *cueFilename;
	char cuePathBuf[PATH_MAX];
	char *cuePath;

	sOutFilename[0] = '\0';

	/* Tell them what I am. */
	printf("\n%s, %s", __DATE__, __TIME__);
	printf("\nbin2iso V1.9b - Converts RAW format (.bin) files to ISO/WAV format");
	printf("\n               Bob Doiron, ICQ#280251                     \n");
	printf("\nCheck for updates at http://users.andara.com/~doiron\n\n");
	if (argc < 2) {
		printf("Usage: bin2iso <cuefile> [<output dir>] [-[a]wg] [-t XX] [-i] [-nob]\n");
		printf("or   : bin2iso <cuefile> -c <binfile>\n");
		printf("\n");
		printf("Where:\n");
		printf("   <cuefile>    - the .cue file that belongs to the .bin file to \n");
		printf("                  be converted\n");
		printf("   <output dir> - the output directory (defaults to current dir) \n");
		printf("   -nwg         - indicates that audio data found in the track   \n");
		printf("                  'gaps' shouldn't be appended to the audio tracks\n");
		printf("   -awg         - looks for non-zero data in the 'gaps', if found\n");
		printf("                  then gaps are appended to audio tracks. Looks  \n");
		printf("                  for more than 1/2 of a sector of non-zero values\n");
		printf("                  (%d values),                                   \n",
		       SIZERAW / 2 / 2);
		printf("   -t XX        - Extracts the XX'th track.                      \n");
		printf("   -i           - Performs the conversion 'in place'. Meaning it \n");
		printf("                  truncates the binfile after each track is      \n");
		printf("                  created to minimize diskspace requirements.    \n");
		printf("                  [not valid with -t]                            \n");
		printf("   -nob         - Doesn't use overburn data past %d sectors.     \n",
		       CD74_MAX_SECTORS);
		printf("                  This of course presumes that the data is not   \n");
		printf("                  useful.                                        \n");
		printf("   -c           - Attempts to create a <cuefile> from an existing\n");
		printf("                  <binfile>                                      \n");
		exit(1);
	}

	strcpy(sOutdir, "./");	// default path

	printf("\n");
	for (i = 2; i < argc; i++) {
		if (argv[i][0] == '-') {
			/* if (strncmp(&(argv[i][1]), "wg", 2)==0) {
			   writegap = 1; 
			   } else */

			if (strncmp(&(argv[i][1]), "awg", 3) == 0) {
				writegap = -1;
				printf("Note: Auto-detecting pregap data\n");
			} else if (strncmp(&(argv[i][1]), "nwg", 3) == 0) {
				writegap = 0;
			} else if (strncmp(&(argv[i][1]), "m2to1", 5) == 0) {
				mode2to1 = 1;
				printf("Note: Converting Mode2 ISO to Mode1\n");
			} else if (strncmp(&(argv[i][1]), "t", 1) == 0) {
				strcpy(sTrack, argv[i + 1]);
				doOneTrack = 1;
				i++;
			} else if (strncmp(&(argv[i][1]), "i", 1) == 0) {
				if (doOneTrack == 1) {
					printf("Invalid combination of options...\n");
					exit(1);
				}
				printf("Bin file will be truncated after each track created\n");
				doInPlace = 1;
			} else if (strncmp(&(argv[i][1]), "c", 1) == 0) {
				createCue = 1;
				strcpy(sBinFilename, argv[i + 1]);
				i++;
			} else if (strncmp(&(argv[i][1]), "nob", 3) == 0) {
				no_overburn = 1;
			}
		} else {
			strcpy(sOutdir, argv[2]);
		}
	}
	cueFilename = argv[1];
	strcpy(cuePathBuf, cueFilename);
	cuePath = dirname(cuePathBuf);

	if (createCue == 1) {
		fdBinFile = fopen(sBinFilename, "rb");
		if (fdBinFile == NULL) {
			printf("Unable to open %s\n", sBinFilename);
			exit(1);
		}
		fdCueFile = fopen(cueFilename, "w");
		if (fdCueFile == NULL) {
			printf("Unable to create %s\n", cueFilename);
			exit(1);
		}

		if ((strcmp(&sBinFilename[strlen(sBinFilename) - 4], ".wav") == 0) ||
		    (strcmp(&sBinFilename[strlen(sBinFilename) - 4], ".WAV") == 0)) {
			printf(".wav binfile - Skipping wav header\n");
			fread(sLine, 1, sizeof(tWavHead), fdBinFile);
		}

		doCueFile();

	} else {
		fdCueFile = fopen(cueFilename, "r");
		if (fdCueFile == NULL) {
			printf("Unable to open %s\n", cueFilename);
			exit(1);
		}
		// get bin filename from cuefile... why? why not.
		if (!fgets(sLine, 256, fdCueFile)) {
			printf("Error Reading Cuefile\n");
			exit(1);
		}
		if (strncmp(sLine, "FILE ", 5) == 0) {
			i = 0;
			j = 0;
			q = 0;	// track open and closed quotes
			do {
				sBinFilename[j] = sLine[5 + i];
				i++;
				j++;
				if ((sBinFilename[j - 1] == '\\') || (sBinFilename[j - 1] == '/')) {
					j = 0;
				}	//strip out path info
				if (sBinFilename[j - 1] == '"') {
					j--;
					q++;
				}	// strip out quotes
			} while ((sLine[5 + i - 1] != ' ') || (q == 1));
			sBinFilename[j] = '\0';
			//bug?? Why did a trailing space show up??
			while (sBinFilename[--j] == ' ')
				sBinFilename[j] = '\0';
		} else {
			printf("Error: Filename not found on first line of cuefile.\n");
			exit(1);
		}

		// Open the bin file
		if (doInPlace == 1) {
			fdBinFile = fopen_filecase(cuePath, sBinFilename, "rb+");
		} else {
			fdBinFile = fopen_filecase(cuePath, sBinFilename, "rb");
		}
		if (fdBinFile == NULL) {
			printf("Unable to open %s\n", sBinFilename);
			perror("\nbin2iso(fopen)");
			exit(1);
		}
		// Get next line
		if (!fgets(sLine, 256, fdCueFile)) {
			printf("Error Reading Cuefile\n");
			exit(1);
		}

		if (strlen(sOutdir) > 0) {
			if ((sOutdir[strlen(sOutdir) - 1] != '/')
			    && (sOutdir[strlen(sOutdir) - 1] != ':')) {
				strcat(sOutdir, "/");
			}
		}

		while (!feof(fdCueFile)) {
			getTrackinfo(sLine, &tracks[nTracks++]);
		}
		tracks[nTracks].idx0 = tracks[nTracks].idx1 = -1;

		switch (tracks[0].mode) {
		case MODE1_2048:
			tracks[0].offset0 = tracks[0].idx0 * SIZEISO_MODE1;
			break;
		case MODE2_2336:
			tracks[0].offset0 = tracks[0].idx0 * SIZEISO_MODE2_FORM2;
			break;
		default:	// AUDIO, MODE1_2352, MODE2_2352:
			tracks[0].offset0 = tracks[0].idx0 * SIZERAW;
			break;
		}
		/* set offsets */

		if (0 != fseek(fdBinFile, 0, SEEK_END)) {
			perror("\nbin2iso(fseek)");
			exit(1);
		}

		tracks[nTracks].offset0 = tracks[nTracks].offset1 = ftell(fdBinFile);

		for (i = 0; i < nTracks; i++) {
			switch (tracks[i].mode) {
			case MODE1_2048:
				tracks[i].offset1 =
				    tracks[i].offset0 + (tracks[i].idx1 -
							 tracks[i].idx0) * SIZEISO_MODE1;
				if (tracks[i + 1].idx0 != -1)
					tracks[i + 1].offset0 =
					    tracks[i].offset1 + (tracks[i + 1].idx0 -
								 tracks[i].idx1) * SIZEISO_MODE1;
				else {
					tracks[i + 1].idx0 = tracks[i + 1].idx1 =
					    (tracks[i + 1].offset0 -
					     tracks[i].offset1) / SIZEISO_MODE1 + tracks[i].idx1;
					if (((tracks[i + 1].offset0 -
					      tracks[i].offset1) % SIZEISO_MODE1) != 0)
						printf
						    ("Warning: Bin file has invalid byte count for cuefile.\n");
				}
				break;
			case MODE2_2336:
				tracks[i].offset1 =
				    tracks[i].offset0 + (tracks[i].idx1 -
							 tracks[i].idx0) * SIZEISO_MODE2_FORM2;
				if (tracks[i + 1].idx0 != -1)
					tracks[i + 1].offset0 =
					    tracks[i].offset1 + (tracks[i + 1].idx0 -
								 tracks[i].idx1) *
					    SIZEISO_MODE2_FORM2;
				else {
					tracks[i + 1].idx0 = tracks[i + 1].idx1 =
					    (tracks[i + 1].offset0 -
					     tracks[i].offset1) / SIZEISO_MODE2_FORM2 +
					    tracks[i].idx1;
					if (((tracks[i + 1].offset0 -
					      tracks[i].offset1) % SIZEISO_MODE2_FORM2) != 0)
						printf
						    ("Warning: Bin file has invalid byte count for cuefile.\n");
				}
				break;
			default:	// AUDIO, MODE1_2352, MODE2_2352:
				tracks[i].offset1 =
				    tracks[i].offset0 + (tracks[i].idx1 - tracks[i].idx0) * SIZERAW;
				if (tracks[i + 1].idx0 != -1)
					tracks[i + 1].offset0 =
					    tracks[i].offset1 + (tracks[i + 1].idx0 -
								 tracks[i].idx1) * SIZERAW;
				else {
					tracks[i + 1].idx0 = tracks[i + 1].idx1 =
					    (tracks[i + 1].offset0 - tracks[i].offset1) / SIZERAW +
					    tracks[i].idx1;
					if (((tracks[i + 1].offset0 -
					      tracks[i].offset1) % SIZERAW) != 0)
						printf
						    ("Warning: Bin file has invalid byte count for cuefile.\n");
				}
				break;
			}
		}

		// if not allowing overburn, then create a new track to hold extra data...
		if (no_overburn == 1) {
			i = nTracks;
			if (tracks[i].idx0 > CD74_MAX_SECTORS) {
				tracks[i + 1] = tracks[nTracks];
				strcpy(tracks[i].name, "obdatatemp.bin");
				tracks[i].idx0 = CD74_MAX_SECTORS;
				tracks[i].idx1 = CD74_MAX_SECTORS;
				switch (tracks[i - 1].mode) {
				case MODE1_2048:
					tracks[i].offset0 =
					    tracks[i - 1].offset1 + (tracks[i].idx0 -
								     tracks[i -
									    1].idx1) *
					    SIZEISO_MODE1;
					break;
				case MODE2_2336:
					tracks[i].offset0 =
					    tracks[i - 1].offset1 + (tracks[i].idx0 -
								     tracks[i -
									    1].idx1) *
					    SIZEISO_MODE2_FORM2;
					break;
				default:	// AUDIO, MODE1_2352, MODE2_2352:
					tracks[i].offset0 =
					    tracks[i - 1].offset1 + (tracks[i].idx0 -
								     tracks[i - 1].idx1) * SIZERAW;
					break;
				}
				tracks[i].offset1 = tracks[i].offset0;
				tracks[i].mode = tracks[i - 1].mode;
				nTracks++;
			}
		}

		/* set sizes */
		for (i = 0; i < nTracks; i++) {
			switch (tracks[i].mode) {
			case MODE1_2352:
				tracks[i].size =
				    ((tracks[i + 1].offset1 -
				      tracks[i].offset1) / SIZERAW) * SIZEISO_MODE1;
				break;
			case MODE2_2336:
				tracks[i].size =
				    ((tracks[i + 1].offset1 -
				      tracks[i].offset1) / SIZEISO_MODE2_FORM2) * SIZERAW;
				break;
			default:	// MODE1_2048, MODE2_2352, AUDIO
				tracks[i].size = tracks[i + 1].offset1 - tracks[i].offset1;
				break;
			}
		}

		if (writegap == -1) {
			writegap = checkGaps(fdBinFile, tracks, nTracks);
		}

		if (writegap == 1)
			printf("Note: Appending pregap data to end of audio tracks\n");
		else
			printf("Note: Discarding pregap data\n");

		printf("\n");
		for (i = 0; i <= nTracks - 1; i++) {
			printf("%s (%3ld Mb) - sectors %06ld:%06ld (offset %09ld:%09ld)\n",
			       tracks[i].name,
			       tracks[i].size / (1024 * 1024),
			       tracks[i].idx1,
			       (((writegap == 0)
				 || (tracks[i].mode !=
				     AUDIO)) ? tracks[i + 1].idx0 : tracks[i + 1].idx1) - 1,
			       tracks[i].offset1,
			       (((writegap == 0)
				 || (tracks[i].mode !=
				     AUDIO)) ? tracks[i + 1].offset0 : tracks[i + 1].offset1) - 1);
		}
		printf("\n");

		if ((((mode2to1 != 1) && (tracks[0].mode == MODE2_2352))
		     || (tracks[0].mode == MODE1_2048)) && (nTracks == 1)) {
			if (tracks[0].mode == MODE2_2352) {
				printf("Mode2/2352");
			}
			if (tracks[0].mode == MODE1_2048) {
				printf("Mode1/2048");
			}
			printf(" single track bin file indicated by cue file\n");
			fclose(fdBinFile);
			if (0 != rename(sBinFilename, tracks[0].name)) {
				perror("\nbin2iso(rename)");
				exit(1);
			}
			printf("%s renamed to %s\n", sBinFilename, tracks[0].name);
			fclose(fdCueFile);
			return (0);
		}

		for (i = nTracks - 1; i >= 0; i--) {
			trackA = tracks[i];
			trackB = tracks[i + 1];
			// audio can't be done in the bin file due to header.
			// 2336 can't either because it's expanded to 2352
			if ((doInPlace == 1) && (i == 0) && (trackA.mode != AUDIO)
			    && (trackA.mode != MODE2_2336)) {
				sOutFilename[0] = '\0';
			} else {
				strcpy(sOutFilename, sOutdir);
				strcat(sOutFilename, trackA.name);
			}
			if (((doOneTrack == 1) && strcmp(trackA.num, sTrack) == 0)
			    || (doOneTrack == 0)) {

				if (!
				    ((i == 0)
				     && ((trackA.mode == MODE2_2352) || (trackA.mode == MODE1_2048))
				     && (doInPlace == 1))) {
					if (!writegap || (trackA.mode != AUDIO)) {	// when not Audio, don't append.
						dotrack(trackA.mode, trackA.idx0, trackA.idx1,
							trackB.idx0, trackA.offset1);
					} else {
						/* if(trackA.idx0 == 0) // handles first track with pregap.
						   dotrack(trackA.mode,           0, trackA.idx1, trackB.idx1, trackA.offset1);
						   else
						 */
						dotrack(trackA.mode, trackA.idx1, trackA.idx1,
							trackB.idx1, trackA.offset1);
					}
				}
			}	/*else {
				   fclose(fdBinFile); // just close bin file. Already MODE1_2048 or MODE2_2352
				   } */
			if ((doOneTrack == 0) && (doInPlace == 1)) {
				if ((i != 0)
				    || ((i == 0)
					&& ((trackA.mode == AUDIO)
					    || (trackA.mode == MODE2_2336)))) {
					printf("Truncating bin file to %ld bytes\n",
					       trackA.offset1);
					if (-1 == ftruncate(fileno(fdBinFile), trackA.offset1)) {
						perror("\nbin2iso(_chsize)");
						exit(1);
					}
				} else {
					printf("Renaming %s to %s\n", sBinFilename, trackA.name);
					fclose(fdBinFile);
					if (0 != rename(sBinFilename, trackA.name)) {
						perror("\nbin2iso(rename)");
						exit(1);
					}
					// fix writepos for case when simply truncating...
					if ((trackA.mode == MODE2_2352)
					    || (trackA.mode == MODE1_2048)) {
						writepos = trackB.offset0;
					}

					printf("Truncating to %ld bytes\n", writepos);

					fdBinFile = fopen(trackA.name, "rb+");	// gets closed in doTrack...
					if (fdBinFile == NULL) {
						perror("bin2iso(fopen)");
						exit(1);
					}

					if (-1 == ftruncate(fileno(fdBinFile), writepos)) {
						perror("\nbin2iso(_chsize)");
						exit(1);
					}
				}
			}
		}
	}
	fclose(fdCueFile);
	fclose(fdBinFile);

	return 0;
}
